/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2013, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.classic.db;

import ch.qos.logback.classic.spi.*;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.db.ConnectionSource;
import com.sinosoft.hydra.um.LoginUser;
import cn.com.sinux.spring.commons.SpringUtils;
import com.sinosoft.hydra.um.domain.permission.SysLogErrorMessage;
import com.sinosoft.hydra.um.domain.permission.SysLogInfo;
import com.sinosoft.hydra.um.domain.permission.SysLogProperty;
import com.sinosoft.hydra.um.helper.AuthContextHelper;
import com.sinosoft.hydra.um.repositories.SysLogErrorMessageRepository;
import com.sinosoft.hydra.um.repositories.SysLogInfoRepository;
import com.sinosoft.hydra.um.repositories.SysLogPropertyRepository;

import java.sql.SQLException;
import java.util.*;

/**
 * The DBAppender inserts logging events into three database tables in a format
 * independent of the Java programming language.
 * <p/>
 * For more information about this appender, please refer to the online manual
 * at http://logback.qos.ch/manual/appenders.html#DBAppender
 *
 * @author Ceki G&uuml;lc&uuml;
 * @author Ray DeCampo
 * @author S&eacute;bastien Pennec
 */
public class DBAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {


    static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();



    public void append(ILoggingEvent eventObject) {

        try {
            String eventId;
            // inserting an event and getting the result must be exclusive
            synchronized (this) {
                eventId = subAppend(eventObject);
//                eventId = selectEventId(insertStatement, connection);
        }
            secondarySubAppend(eventObject, eventId);
        } catch (Throwable sqle) {
            addError("problem appending event", sqle);
        }
    }

    /**
     * @param connectionSource The connectionSource to set.
     */
    public void setConnectionSource(ConnectionSource connectionSource) {

    }



    public void start() {
        super.start();
    }

    protected String subAppend(ILoggingEvent event) throws Throwable {

        SysLogInfo sysLogInfo = new SysLogInfo();
        bindLoggingEventWithInsertStatement(sysLogInfo, event);

        // This is expensive... should we do it every time?
        bindCallerDataWithPreparedStatement(sysLogInfo, event.getCallerData());
        //添加当前用户用户名和ip地址
        LoginUser loginUser = AuthContextHelper.getLoginUser();
        if(loginUser != null){
            sysLogInfo.setUserName(loginUser.getUserName());
            sysLogInfo.setIpAddress(loginUser.getClientIp());
        }
        SpringUtils.getBean(SysLogInfoRepository.class).save(sysLogInfo);
//        int updateCount = insertStatement.executeUpdate();
//        if (updateCount != 1) {
//            addWarn("Failed to insert loggingEvent");
//        }
        return sysLogInfo.getId();
    }

    protected void secondarySubAppend(ILoggingEvent event,
                                      String eventId) throws Throwable {
        Map<String, String> mergedMap = mergePropertyMaps(event);
        insertProperties(mergedMap, eventId);

        if (event.getThrowableProxy() != null) {
            insertThrowable(event.getThrowableProxy(), eventId);
        }
    }

    void bindLoggingEventWithInsertStatement(SysLogInfo stmt,
                                             ILoggingEvent event) throws SQLException {
        stmt.setCreateDate(new Date(event.getTimeStamp()));
        stmt.setMsg(event.getFormattedMessage());
        stmt.setRecorder(event.getLoggerName());
        stmt.setLevel(event.getLevel().toString());
        stmt.setThreadName(event.getThreadName());
    }


    void bindCallerDataWithPreparedStatement(SysLogInfo stmt,
                                             StackTraceElement[] callerDataArray) throws SQLException {

        StackTraceElement caller = extractFirstCaller(callerDataArray);
        stmt.setFile(caller.getFileName());
        stmt.setEntityClass(caller.getClassName());
        stmt.setMethod(caller.getMethodName());
        stmt.setLine(Integer.toString(caller.getLineNumber()));
    }

    private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
        StackTraceElement caller = EMPTY_CALLER_DATA;
        if (hasAtLeastOneNonNullElement(callerDataArray))
            caller = callerDataArray[0];
        return caller;
    }

    private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
        return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
    }

    Map<String, String> mergePropertyMaps(ILoggingEvent event) {
        Map<String, String> mergedMap = new HashMap<String, String>();
        // we add the context properties first, then the event properties, since
        // we consider that event-specific properties should have priority over
        // context-wide properties.
        Map<String, String> loggerContextMap = event.getLoggerContextVO()
                .getPropertyMap();
        Map<String, String> mdcMap = event.getMDCPropertyMap();
        if (loggerContextMap != null) {
            mergedMap.putAll(loggerContextMap);
        }
        if (mdcMap != null) {
            mergedMap.putAll(mdcMap);
        }

        return mergedMap;
    }

    protected void insertProperties(Map<String, String> mergedMap, String eventId) throws SQLException {
        Set<String> propertiesKeys = mergedMap.keySet();
        if (propertiesKeys.size() > 0) {
            List<SysLogProperty> sysLogProperties = new ArrayList<SysLogProperty>();
            for (String key : propertiesKeys) {
                String value = mergedMap.get(key);
                SysLogProperty sysLogProperty = new SysLogProperty();
                sysLogProperty.setId(eventId);
                sysLogProperty.setIp(key);
                sysLogProperty.setName(value);
                sysLogProperties.add(sysLogProperty);
            }

            if (sysLogProperties.size() > 0) {
                SpringUtils.getBean(SysLogPropertyRepository.class).save(sysLogProperties);
            }
        }
    }

    /**
     * Add an exception statement either as a batch or execute immediately if
     * batch updates are not supported.
     */
    SysLogErrorMessage updateExceptionStatement(String txt, short i, String eventId) throws SQLException {
        SysLogErrorMessage sysLogErrorMessage = new SysLogErrorMessage();
        sysLogErrorMessage.setEventId(eventId);
        sysLogErrorMessage.setIndx(2);
        sysLogErrorMessage.setMsg(txt);
        return sysLogErrorMessage;
    }

    short buildExceptionStatement(IThrowableProxy tp, short baseIndex, String eventId)
            throws SQLException {

        StringBuilder buf = new StringBuilder();
        ThrowableProxyUtil.subjoinFirstLine(buf, tp);
        List<SysLogErrorMessage> sysLogErrorMessages = new ArrayList<SysLogErrorMessage>();
        sysLogErrorMessages.add(updateExceptionStatement(buf.toString(),
                baseIndex++, eventId));
        int commonFrames = tp.getCommonFrames();
        StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
        for (int i = 0; i < stepArray.length - commonFrames; i++) {
            StringBuilder sb = new StringBuilder();
            sb.append(CoreConstants.TAB);
            ThrowableProxyUtil.subjoinSTEP(sb, stepArray[i]);
            sysLogErrorMessages.add(updateExceptionStatement(sb.toString(),
                    baseIndex++, eventId));
        }

        if (commonFrames > 0) {
            StringBuilder sb = new StringBuilder();
            sb.append(CoreConstants.TAB).append("... ").append(commonFrames).append(
                    " common frames omitted");
            sysLogErrorMessages.add(updateExceptionStatement(sb.toString(),
                    baseIndex++, eventId));
        }
        SpringUtils.getBean(SysLogErrorMessageRepository.class).save(sysLogErrorMessages);
        return baseIndex;
    }

    protected void insertThrowable(IThrowableProxy tp,
                                   String eventId) throws SQLException {

        short baseIndex = 0;
        while (tp != null) {
            baseIndex = buildExceptionStatement(tp, baseIndex,
                    eventId);
            tp = tp.getCause();
        }

    }
}
